#ifndef _SESERIALIZEYAML_UTIL_H_
#define _SESERIALIZEYAML_UTIL_H_
#include <string>
#include <yaml-cpp/yaml.h>
#include <SEngine/Ecs/TypeRegister.h>
#include <SEngine/Ecs/Components/AllComponents.h>
#include <SECore/Manager.h>
#include <SERender/Resourses/Loader/TextureLoader.h>
#include <SECore/ResourcePayload.h>
#include <SESerializeYAML/ModelSerializer.h>

namespace SEngine
{

    template<typename T>
    struct is_Variant : std::false_type {};

    template<typename... Ts>
    struct is_Variant<std::variant<Ts...>> : std::true_type {};

    template<typename T>
    constexpr bool is_Variant_v = is_Variant<T>::value;

    std::string YAMLNodeToString(YAML::Node &node);


    template<typename T>
    inline void ElementSerialize(YAML::Node & node, const std::string & name, T & data) { static_assert(false, __FUNCTION__ " is not impl!"); }

    template<typename T>
    inline const std::string BaseSerialize(T & t)
    {
        YAML::Node node;
        Reflect<T> info;
        YAML::Node fields;
        info.ForeachField([&fields, &t](auto & field) {
            if constexpr (REF_IS_REFLECTABLE(field)) {
                fields[field.name] = YAML::Load(BaseSerialize(field.Value(t)));
            } else {
                ElementSerialize<std::decay_t<decltype(field)>::Type>(fields, field.name, field.Value(t));
            }
        });
        node[info.Name()] = fields;
        return YAMLNodeToString(node);
    }

    template<typename T>
    inline void ElementSerialize(YAML::Node & node, const std::string & name, 
        std::enable_if_t< 
            std::is_same_v<T, glm::mat3> ||
            std::is_same_v<T, glm::mat4> 
        , T> & data) {}

    template<typename T>
    inline void ElementSerialize(YAML::Node & node, const std::string & name, 
        std::enable_if_t< 
            std::is_same_v<T, bool> ||
            std::is_same_v<T, int> ||
            std::is_same_v<T, float> ||
            std::is_same_v<T, std::string>
        , T> & data) {
        node[name] = data;   
    }

    template<typename T>
    inline void ElementSerialize(YAML::Node & node, const std::string &name, 
        std::enable_if_t<std::is_same_v<T, glm::vec2> || std::is_same_v<T, glm::vec3> || std::is_same_v<T, glm::vec4>, T> & data) {
        node[name] = YAML::Load(BaseSerialize(data));
    }
    
    template<>
    inline void ElementSerialize<Shader::Sampler>(YAML::Node & node, const std::string &name, Shader::Sampler & data) {
        node[name] = data.path;
    }

    template<>
    inline void ElementSerialize<Model>(YAML::Node & node, const std::string &name, Model & data) {
        node[name] = YAML::Load(data.Serialize());
    }

    template<typename T>
    inline void ElementSerialize(YAML::Node & node, const std::string & name, 
        std::enable_if_t<is_ResourcePayloadRef_v<T>, T> & data) {
        node[name] = !data ? "Null" : data.Name();
    }

    template<typename T>
    inline void ElementSerialize(YAML::Node & node, const std::string & name,
        std::enable_if_t<is_Variant_v<T>, T> & data) {
            std::visit([&](auto elem) {
                if constexpr (Reflect<std::decay_t<decltype(elem)>>::Reflectable()) {
                    BaseSerialize(elem);
                } else {
                    ElementSerialize<std::decay_t<decltype(elem)>>(node, name, elem);
                }
            }, data);
    }





    template<typename T>
    inline void ElementDeserialize(YAML::Node & node, const std::string & name, T & data) { static_assert(false, __FUNCTION__ " is not impl!"); }

    template<typename T>
    inline void BaseDeserialize(const std::string & str, T & t) 
    { 
        Reflect<T> info;
        YAML::Node node = YAML::Load(str)[info.Name()];
        info.ForeachField([&node, &t](auto & field) {
            if constexpr (REF_IS_REFLECTABLE(field)) {
                BaseDeserialize(YAMLNodeToString(node[field.name]), field.Value(t));
            } else {
                ElementDeserialize<std::decay_t<decltype(field)>::Type>(node, field.name, field.Value(t));
            }
        });
    }

    template<typename T>
    inline void ElementDeserialize(YAML::Node & node, const std::string & name, 
        std::enable_if_t< 
            std::is_same_v<T, glm::mat3> || 
            std::is_same_v<T, glm::mat4>
        , T> & data) {}

    template<typename T>
    inline void ElementDeserialize(YAML::Node & node, const std::string & name, 
        std::enable_if_t< 
            std::is_same_v<T, bool> || 
            std::is_same_v<T, int> ||
            std::is_same_v<T, float> ||
            std::is_same_v<T, std::string>
        , T> & data) {
        data = node[name].as<T>();
    }

    template<typename T>
    inline void ElementDeserialize(YAML::Node & node, const std::string &name, 
        std::enable_if_t<std::is_same_v<T, glm::vec2> || std::is_same_v<T, glm::vec3> || std::is_same_v<T, glm::vec4>, T> & data) {
        BaseDeserialize(YAMLNodeToString(node[name]), data);
    }


    template<>
    inline void ElementDeserialize<Shader::Sampler>(YAML::Node & node, const std::string &name, 
        Shader::Sampler & data) {
        std::string path = node[name].as<std::string>();
        data.path = path;
        data.texture = Manager::GetInstance().Get<Texture>(path).value().Value();
    }

    template<>
    inline void ElementDeserialize<Model>(YAML::Node & node, const std::string &name, Model & data) {
        data.Deserialize(YAMLNodeToString(node[name]));
    }

    template<typename T>
    inline void ElementDeserialize(YAML::Node & node, const std::string & name, 
        std::enable_if_t<is_ResourcePayloadRef_v<T>, T> & data) {
        std::string path = node[name].as<std::string>();
        if (path != "Null") {
            data = Manager::GetInstance().Get<T::Type>(path).value();
        }
    }

    template<typename T>
    inline void ElementDeserialize(YAML::Node & node, const std::string & name,
        std::enable_if_t<is_Variant_v<T>, T> & data) {
            std::visit([&](auto elem) {
                if constexpr (Reflect<std::decay_t<decltype(elem)>>::Reflectable()) {
                    BaseDeserialize(YAMLNodeToString(node[name]), elem);
                } else {
                    ElementDeserialize<std::decay_t<decltype(elem)>>(node, name, elem);
                }
            }, data);
    }


    
} // namespace SEngine


#endif